#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

using Osh.Media;

namespace Osh.ProgDvb
{
	/// <summary>
	/// Summary description for ChannelDatabase.
	/// </summary>
	public class ChannelDatabase : IMediaLibrary
	{
		[DllImport("kernel32.dll")]
		private static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName,
			string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName);
		[DllImport("kernel32.dll")]
		private static extern uint GetPrivateProfileInt(string lpAppName, string lpKeyName,
			int nDefault, string lpFileName);

		private static string MediaLibraryName = "ProgDVB";

		private string _path;
		private int _version;
		private SortedList _channelIds;
		private SortedList _channelIdsNoPcr;
		private List<IMedia> _channelList;
		private MediaCollection _readOnlyChannelList;
		private List<IMediaTree> _channelTreeList;
		private MediaTreeCollection _readOnlyChannelTreeList;

		public ChannelDatabase(string databaseFolder)
		{
			Load(databaseFolder);
		}

		public void Reload()
		{
			Load(_path);
		}

		private void Load(string databaseFolder)
		{
			_path = databaseFolder;
			_channelIds = new SortedList();
			_channelIdsNoPcr = new SortedList();
			_channelList = new List<IMedia>();
			_readOnlyChannelList = new MediaCollection(_channelList);
			_channelTreeList = new List<IMediaTree>();
			_readOnlyChannelTreeList = new MediaTreeCollection(_channelTreeList);

			string databasePath = System.IO.Path.Combine(databaseFolder, "Channels.dat");
			ReadChannelDatabase(databasePath);

			string iniFilePath = System.IO.Path.Combine(databaseFolder, "Channels.ini");
			string[] channelTreeFiles = GetChannelTreeFiles(iniFilePath);

			foreach(string channelTreeFile in channelTreeFiles)
			{
				string channelTreeFilePath = System.IO.Path.Combine(databaseFolder, channelTreeFile);
				if(!File.Exists(channelTreeFilePath))
					continue;

				ChannelTree channelTree = new ChannelTree(this, channelTreeFilePath);
				_channelTreeList.Add(channelTree);
			}
		}

		private string[] GetChannelTreeFiles(string iniFilePath)
		{
			if(!File.Exists(iniFilePath))
				throw new FileNotFoundException(
					"Cannot find ProgDVB channel database initialization file.", iniFilePath);

			uint channelTreeCount = GetPrivateProfileInt("ChannelTreeCollection", "Count", 0, iniFilePath);
			
			ArrayList channelTreeFiles = new ArrayList((int)channelTreeCount);
			for(uint i = 1; i <= channelTreeCount; i++)
			{
				StringBuilder sb = new StringBuilder(255);
				GetPrivateProfileString("ChannelTreeCollection", "name" + i, "",
					sb, (uint)sb.Capacity, iniFilePath);

				string channelTreeFile = sb.ToString();
				if(channelTreeFile.Length > 0 &&
					string.Equals(System.IO.Path.GetExtension(channelTreeFile), ".tree", StringComparison.CurrentCultureIgnoreCase))
					channelTreeFiles.Add(sb.ToString());
			}

			return (string[])channelTreeFiles.ToArray(typeof(string));
		}

		private void ReadChannelDatabase(string databasePath)
		{
			if(!File.Exists(databasePath))
				throw new FileNotFoundException("Cannot find ProgDVB channel database file.", databasePath);

			using(FileStream stream =
				new FileStream(databasePath, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				using(BinaryReader reader = new BinaryReader(stream, Encoding.Unicode))
				{
					ReadChannelDatabase(reader);
				}
			}
		}
		
		private void ReadChannelDatabase(BinaryReader reader)
		{
			uint id = reader.ReadUInt32();
			if(id != 0x22DFBC3B)
				throw new ProgDvbException(
					"The ProgDVB channel database specified in the configuration file is corrupted.");

			_version = (int)reader.ReadUInt32();
			if(_version < 0x71)
				throw new ProgDvbException(
					string.Format("Version {0} of the ProgDVB channel database is not supported.\n" +
						"To update the channel database version please open any channel list properties dialog, " +
						"press OK and than restart ProgDVB application.", _version));

			if(_version > 0x72)
				throw new ProgDvbException(
					string.Format("Version {0} of the ProgDVB channel database is not supported.\n" +
						"Please upgrade OSH.", _version));

			uint headerSize = reader.ReadUInt32();
			uint fileSize = reader.ReadUInt32();
			int channelCount = (int)reader.ReadUInt32();
			int index = 0;
			while(channelCount-- > 0)
			{
				Channel channel = new Channel(this, reader);
				_channelList.Add(channel);
				
				// Might be duplicated channelId
				try { _channelIds.Add(channel.Id, index); } catch(ArgumentException) {}
				try
				{
					ChannelId _channelIdNoPcr = channel.Id;
					_channelIdNoPcr.Pcr = 0;
					_channelIdsNoPcr.Add(_channelIdNoPcr, index);
				} catch(ArgumentException) {}

				index++;
			}
		}

		/// <summary>
		/// Returns channel by its channelId.
		/// </summary>
		/// <param name="channelId"></param>
		/// <returns></returns>
		public Channel GetChannel(string channelId)
		{
			return GetChannel(new ChannelId(channelId));
		}

		[ComVisible(false)]
		public Channel GetChannel(ChannelId channelId)
		{
			Channel channel = null;
			if(TryGetChannel(channelId, out channel))
				return channel;
			else
				throw new ArgumentOutOfRangeException("channelId");
		}

		public bool TryGetChannel(Uri channelUri, out Channel channel)
		{
			try
			{
				ChannelId channelId = Channel.UriToChannelId(channelUri);
				return TryGetChannel(channelId, out channel);
			}
			catch(Exception) { }
			
			channel = null;
			return false;
		}

		public bool TryGetChannel(ChannelId channelId, out Channel channel)
		{
			int index = _channelIds.IndexOfKey(channelId);
			if(index != -1)
			{
				channel = (Channel)_channelList[(int)_channelIds.GetByIndex(index)];
				return true;
			}

			//if(_version >= 0x72)
			{
				ChannelId channelIdNoPcr = channelId;
				channelIdNoPcr.Pcr = 0;
				index = _channelIdsNoPcr.IndexOfKey(channelIdNoPcr);
				if(index != -1)
				{
					channel = (Channel)_channelList[(int)_channelIdsNoPcr.GetByIndex(index)];
					return true;
				}
			}

			channel = null;
			return false;
		}

		public string Path
		{
			get { return _path; }
		}

		public int Version
		{
			get { return _version; }
		}

		public MediaCollection Channels
		{
			get { return _readOnlyChannelList; }
		}

		#region IMediaLibrary
		public string Name
		{
			get { return MediaLibraryName; }
		}

		public Type DefaultPlayer
		{
			get { return typeof(ProgDvbApplication); }
		}

		public MediaTreeCollection MediaTrees
		{
			get { return _readOnlyChannelTreeList; }
		}

		public IMedia GetMedia(string mediaUri)
		{
			return GetMedia(MediaBase.ParseMediaUri(mediaUri));
		}

		[ComVisible(false)]
		public IMedia GetMedia(Uri mediaUri)
		{
			Channel media;
			if(!TryGetChannel(mediaUri, out media))
				throw new ArgumentOutOfRangeException("mediaUri",
					string.Format("Cannot find media by uri '{0}'.", mediaUri));
			return media;
		}

		public void Save()
		{
			throw new NotSupportedException("Save operation is not supported.");
		}
		#endregion IMediaLibrary

		internal bool SupportsDeviceId
		{
			get { return _version >= 0x72; }
		}

		//internal bool SupportsIptv
		//{
		//    get { return _version >= 0x72; }
		//}

		internal static bool Exists(string databaseFolder)
		{
			string databasePath = System.IO.Path.Combine(databaseFolder, "Channels.dat");
			if(!File.Exists(databasePath))
				return false;

			string iniFilePath = System.IO.Path.Combine(databaseFolder, "Channels.ini");
			if(!File.Exists(iniFilePath))
				return false;

			return true;
		}
	}
}
